// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by the GNU General Public License v2.
//
// Partially adapted from evtest:
// http://cgit.freedesktop.org/evtest
//
// Copyright (c) 1999-2000 Vojtech Pavlik
// Copyright (c) 2009-2011 Red Hat, Inc

#define _GNU_SOURCE // for asprintf


#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <linux/input.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <X11/keysym.h>
#include <X11/Xlib.h>

#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x)  ((x)%BITS_PER_LONG)
#define BIT(x)  (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array)  ((array[LONG(bit)] >> OFF(bit)) & 1)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define LONG_BITS (sizeof(long) * 8)
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)

#define MIN(a, b) ((a) < (b) ? (a) : (b))

#define DEV_INPUT_EVENT "/dev/input"
#define EVENT_DEV_NAME "event"

#ifndef EV_SYN
#define EV_SYN 0
#endif
#ifndef SYN_MT_REPORT
#define SYN_MT_REPORT 2
#endif
#ifndef ABS_MT_SLOT
#define ABS_MT_SLOT 47
#endif

#define MAX_SLOT_COUNT 64

/* Set clockid to be used for timestamps */
#ifndef EVIOCSCLOCKID
#define EVIOCSCLOCKID  _IOW('E', 0xa0, int)
#endif

#define NAME_ELEMENT(element) [element] = #element

// Define the access permission of the screenshot output directory.
#define OUTPUT_DIR_MODE (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
#define DEFAULT_OUTPUT_DIR "/tmp"

static const char * const events[EV_MAX + 1] = {
  [0 ... EV_MAX] = NULL,
  NAME_ELEMENT(EV_SYN), NAME_ELEMENT(EV_KEY), NAME_ELEMENT(EV_REL),
  NAME_ELEMENT(EV_ABS), NAME_ELEMENT(EV_MSC), NAME_ELEMENT(EV_LED),
  NAME_ELEMENT(EV_SND), NAME_ELEMENT(EV_REP), NAME_ELEMENT(EV_FF),
  NAME_ELEMENT(EV_PWR), NAME_ELEMENT(EV_FF_STATUS), NAME_ELEMENT(EV_SW),
};

static const char * const keys[KEY_MAX + 1] = {
  [0 ... KEY_MAX] = NULL,
  NAME_ELEMENT(KEY_RESERVED), NAME_ELEMENT(KEY_ESC),
  NAME_ELEMENT(KEY_1), NAME_ELEMENT(KEY_2), NAME_ELEMENT(KEY_3),
  NAME_ELEMENT(KEY_4), NAME_ELEMENT(KEY_5), NAME_ELEMENT(KEY_6),
  NAME_ELEMENT(KEY_7), NAME_ELEMENT(KEY_8), NAME_ELEMENT(KEY_9),
  NAME_ELEMENT(KEY_0), NAME_ELEMENT(KEY_MINUS), NAME_ELEMENT(KEY_EQUAL),
  NAME_ELEMENT(KEY_BACKSPACE), NAME_ELEMENT(KEY_TAB), NAME_ELEMENT(KEY_Q),
  NAME_ELEMENT(KEY_W), NAME_ELEMENT(KEY_E), NAME_ELEMENT(KEY_R),
  NAME_ELEMENT(KEY_T), NAME_ELEMENT(KEY_Y), NAME_ELEMENT(KEY_U),
  NAME_ELEMENT(KEY_I), NAME_ELEMENT(KEY_O), NAME_ELEMENT(KEY_P),
  NAME_ELEMENT(KEY_LEFTBRACE), NAME_ELEMENT(KEY_RIGHTBRACE),
  NAME_ELEMENT(KEY_ENTER), NAME_ELEMENT(KEY_LEFTCTRL), NAME_ELEMENT(KEY_A),
  NAME_ELEMENT(KEY_S), NAME_ELEMENT(KEY_D), NAME_ELEMENT(KEY_F),
  NAME_ELEMENT(KEY_G), NAME_ELEMENT(KEY_H), NAME_ELEMENT(KEY_J),
  NAME_ELEMENT(KEY_K), NAME_ELEMENT(KEY_L), NAME_ELEMENT(KEY_SEMICOLON),
  NAME_ELEMENT(KEY_APOSTROPHE), NAME_ELEMENT(KEY_GRAVE),
  NAME_ELEMENT(KEY_LEFTSHIFT), NAME_ELEMENT(KEY_BACKSLASH),
  NAME_ELEMENT(KEY_Z), NAME_ELEMENT(KEY_X), NAME_ELEMENT(KEY_C),
  NAME_ELEMENT(KEY_V), NAME_ELEMENT(KEY_B), NAME_ELEMENT(KEY_N),
  NAME_ELEMENT(KEY_M), NAME_ELEMENT(KEY_COMMA), NAME_ELEMENT(KEY_DOT),
  NAME_ELEMENT(KEY_SLASH), NAME_ELEMENT(KEY_RIGHTSHIFT),
  NAME_ELEMENT(KEY_KPASTERISK), NAME_ELEMENT(KEY_LEFTALT),
  NAME_ELEMENT(KEY_SPACE), NAME_ELEMENT(KEY_CAPSLOCK), NAME_ELEMENT(KEY_F1),
  NAME_ELEMENT(KEY_F2), NAME_ELEMENT(KEY_F3), NAME_ELEMENT(KEY_F4),
  NAME_ELEMENT(KEY_F5), NAME_ELEMENT(KEY_F6), NAME_ELEMENT(KEY_F7),
  NAME_ELEMENT(KEY_F8), NAME_ELEMENT(KEY_F9), NAME_ELEMENT(KEY_F10),
  NAME_ELEMENT(KEY_NUMLOCK), NAME_ELEMENT(KEY_SCROLLLOCK),
  NAME_ELEMENT(KEY_KP7), NAME_ELEMENT(KEY_KP8), NAME_ELEMENT(KEY_KP9),
  NAME_ELEMENT(KEY_KPMINUS), NAME_ELEMENT(KEY_KP4), NAME_ELEMENT(KEY_KP5),
  NAME_ELEMENT(KEY_KP6), NAME_ELEMENT(KEY_KPPLUS), NAME_ELEMENT(KEY_KP1),
  NAME_ELEMENT(KEY_KP2), NAME_ELEMENT(KEY_KP3), NAME_ELEMENT(KEY_KP0),
  NAME_ELEMENT(KEY_KPDOT), NAME_ELEMENT(KEY_ZENKAKUHANKAKU),
  NAME_ELEMENT(KEY_102ND), NAME_ELEMENT(KEY_F11), NAME_ELEMENT(KEY_F12),
  NAME_ELEMENT(KEY_RO), NAME_ELEMENT(KEY_KATAKANA),
  NAME_ELEMENT(KEY_HIRAGANA), NAME_ELEMENT(KEY_HENKAN),
  NAME_ELEMENT(KEY_KATAKANAHIRAGANA), NAME_ELEMENT(KEY_MUHENKAN),
  NAME_ELEMENT(KEY_KPJPCOMMA), NAME_ELEMENT(KEY_KPENTER),
  NAME_ELEMENT(KEY_RIGHTCTRL), NAME_ELEMENT(KEY_KPSLASH),
  NAME_ELEMENT(KEY_SYSRQ), NAME_ELEMENT(KEY_RIGHTALT),
  NAME_ELEMENT(KEY_LINEFEED), NAME_ELEMENT(KEY_HOME), NAME_ELEMENT(KEY_UP),
  NAME_ELEMENT(KEY_PAGEUP), NAME_ELEMENT(KEY_LEFT), NAME_ELEMENT(KEY_RIGHT),
  NAME_ELEMENT(KEY_END), NAME_ELEMENT(KEY_DOWN), NAME_ELEMENT(KEY_PAGEDOWN),
  NAME_ELEMENT(KEY_INSERT), NAME_ELEMENT(KEY_DELETE),
  NAME_ELEMENT(KEY_MACRO), NAME_ELEMENT(KEY_MUTE),
  NAME_ELEMENT(KEY_VOLUMEDOWN), NAME_ELEMENT(KEY_VOLUMEUP),
  NAME_ELEMENT(KEY_POWER), NAME_ELEMENT(KEY_KPEQUAL),
  NAME_ELEMENT(KEY_KPPLUSMINUS), NAME_ELEMENT(KEY_PAUSE),
  NAME_ELEMENT(KEY_KPCOMMA), NAME_ELEMENT(KEY_HANGUEL),
  NAME_ELEMENT(KEY_HANJA), NAME_ELEMENT(KEY_YEN),
  NAME_ELEMENT(KEY_LEFTMETA), NAME_ELEMENT(KEY_RIGHTMETA),
  NAME_ELEMENT(KEY_COMPOSE), NAME_ELEMENT(KEY_STOP),
  NAME_ELEMENT(KEY_AGAIN), NAME_ELEMENT(KEY_PROPS), NAME_ELEMENT(KEY_UNDO),
  NAME_ELEMENT(KEY_FRONT), NAME_ELEMENT(KEY_COPY), NAME_ELEMENT(KEY_OPEN),
  NAME_ELEMENT(KEY_PASTE), NAME_ELEMENT(KEY_FIND), NAME_ELEMENT(KEY_CUT),
  NAME_ELEMENT(KEY_HELP), NAME_ELEMENT(KEY_MENU), NAME_ELEMENT(KEY_CALC),
  NAME_ELEMENT(KEY_SETUP), NAME_ELEMENT(KEY_SLEEP),
  NAME_ELEMENT(KEY_WAKEUP), NAME_ELEMENT(KEY_FILE),
  NAME_ELEMENT(KEY_SENDFILE), NAME_ELEMENT(KEY_DELETEFILE),
  NAME_ELEMENT(KEY_XFER), NAME_ELEMENT(KEY_PROG1), NAME_ELEMENT(KEY_PROG2),
  NAME_ELEMENT(KEY_WWW), NAME_ELEMENT(KEY_MSDOS), NAME_ELEMENT(KEY_COFFEE),
  NAME_ELEMENT(KEY_DIRECTION), NAME_ELEMENT(KEY_CYCLEWINDOWS),
  NAME_ELEMENT(KEY_MAIL), NAME_ELEMENT(KEY_BOOKMARKS),
  NAME_ELEMENT(KEY_COMPUTER), NAME_ELEMENT(KEY_BACK),
  NAME_ELEMENT(KEY_FORWARD), NAME_ELEMENT(KEY_CLOSECD),
  NAME_ELEMENT(KEY_EJECTCD), NAME_ELEMENT(KEY_EJECTCLOSECD),
  NAME_ELEMENT(KEY_NEXTSONG), NAME_ELEMENT(KEY_PLAYPAUSE),
  NAME_ELEMENT(KEY_PREVIOUSSONG), NAME_ELEMENT(KEY_STOPCD),
  NAME_ELEMENT(KEY_RECORD), NAME_ELEMENT(KEY_REWIND),
  NAME_ELEMENT(KEY_PHONE), NAME_ELEMENT(KEY_ISO), NAME_ELEMENT(KEY_CONFIG),
  NAME_ELEMENT(KEY_HOMEPAGE), NAME_ELEMENT(KEY_REFRESH),
  NAME_ELEMENT(KEY_EXIT), NAME_ELEMENT(KEY_MOVE), NAME_ELEMENT(KEY_EDIT),
  NAME_ELEMENT(KEY_SCROLLUP), NAME_ELEMENT(KEY_SCROLLDOWN),
  NAME_ELEMENT(KEY_KPLEFTPAREN), NAME_ELEMENT(KEY_KPRIGHTPAREN),
  NAME_ELEMENT(KEY_F13), NAME_ELEMENT(KEY_F14), NAME_ELEMENT(KEY_F15),
  NAME_ELEMENT(KEY_F16), NAME_ELEMENT(KEY_F17), NAME_ELEMENT(KEY_F18),
  NAME_ELEMENT(KEY_F19), NAME_ELEMENT(KEY_F20), NAME_ELEMENT(KEY_F21),
  NAME_ELEMENT(KEY_F22), NAME_ELEMENT(KEY_F23), NAME_ELEMENT(KEY_F24),
  NAME_ELEMENT(KEY_PLAYCD), NAME_ELEMENT(KEY_PAUSECD),
  NAME_ELEMENT(KEY_PROG3), NAME_ELEMENT(KEY_PROG4),
  NAME_ELEMENT(KEY_SUSPEND), NAME_ELEMENT(KEY_CLOSE),
  NAME_ELEMENT(KEY_PLAY), NAME_ELEMENT(KEY_FASTFORWARD),
  NAME_ELEMENT(KEY_BASSBOOST), NAME_ELEMENT(KEY_PRINT),
  NAME_ELEMENT(KEY_HP), NAME_ELEMENT(KEY_CAMERA), NAME_ELEMENT(KEY_SOUND),
  NAME_ELEMENT(KEY_QUESTION), NAME_ELEMENT(KEY_EMAIL),
  NAME_ELEMENT(KEY_CHAT), NAME_ELEMENT(KEY_SEARCH),
  NAME_ELEMENT(KEY_CONNECT), NAME_ELEMENT(KEY_FINANCE),
  NAME_ELEMENT(KEY_SPORT), NAME_ELEMENT(KEY_SHOP),
  NAME_ELEMENT(KEY_ALTERASE), NAME_ELEMENT(KEY_CANCEL),
  NAME_ELEMENT(KEY_BRIGHTNESSDOWN), NAME_ELEMENT(KEY_BRIGHTNESSUP),
  NAME_ELEMENT(KEY_MEDIA), NAME_ELEMENT(KEY_UNKNOWN), NAME_ELEMENT(KEY_OK),
  NAME_ELEMENT(KEY_SELECT), NAME_ELEMENT(KEY_GOTO), NAME_ELEMENT(KEY_CLEAR),
  NAME_ELEMENT(KEY_POWER2), NAME_ELEMENT(KEY_OPTION),
  NAME_ELEMENT(KEY_INFO), NAME_ELEMENT(KEY_TIME), NAME_ELEMENT(KEY_VENDOR),
  NAME_ELEMENT(KEY_ARCHIVE), NAME_ELEMENT(KEY_PROGRAM),
  NAME_ELEMENT(KEY_CHANNEL), NAME_ELEMENT(KEY_FAVORITES),
  NAME_ELEMENT(KEY_EPG), NAME_ELEMENT(KEY_PVR), NAME_ELEMENT(KEY_MHP),
  NAME_ELEMENT(KEY_LANGUAGE), NAME_ELEMENT(KEY_TITLE),
  NAME_ELEMENT(KEY_SUBTITLE), NAME_ELEMENT(KEY_ANGLE),
  NAME_ELEMENT(KEY_ZOOM), NAME_ELEMENT(KEY_MODE),
  NAME_ELEMENT(KEY_KEYBOARD), NAME_ELEMENT(KEY_SCREEN),
  NAME_ELEMENT(KEY_PC), NAME_ELEMENT(KEY_TV), NAME_ELEMENT(KEY_TV2),
  NAME_ELEMENT(KEY_VCR), NAME_ELEMENT(KEY_VCR2), NAME_ELEMENT(KEY_SAT),
  NAME_ELEMENT(KEY_SAT2), NAME_ELEMENT(KEY_CD), NAME_ELEMENT(KEY_TAPE),
  NAME_ELEMENT(KEY_RADIO), NAME_ELEMENT(KEY_TUNER),
  NAME_ELEMENT(KEY_PLAYER), NAME_ELEMENT(KEY_TEXT), NAME_ELEMENT(KEY_DVD),
  NAME_ELEMENT(KEY_AUX), NAME_ELEMENT(KEY_MP3), NAME_ELEMENT(KEY_AUDIO),
  NAME_ELEMENT(KEY_VIDEO), NAME_ELEMENT(KEY_DIRECTORY),
  NAME_ELEMENT(KEY_LIST), NAME_ELEMENT(KEY_MEMO),
  NAME_ELEMENT(KEY_CALENDAR), NAME_ELEMENT(KEY_RED),
  NAME_ELEMENT(KEY_GREEN), NAME_ELEMENT(KEY_YELLOW), NAME_ELEMENT(KEY_BLUE),
  NAME_ELEMENT(KEY_CHANNELUP), NAME_ELEMENT(KEY_CHANNELDOWN),
  NAME_ELEMENT(KEY_FIRST), NAME_ELEMENT(KEY_LAST), NAME_ELEMENT(KEY_AB),
  NAME_ELEMENT(KEY_NEXT), NAME_ELEMENT(KEY_RESTART), NAME_ELEMENT(KEY_SLOW),
  NAME_ELEMENT(KEY_SHUFFLE), NAME_ELEMENT(KEY_BREAK),
  NAME_ELEMENT(KEY_PREVIOUS), NAME_ELEMENT(KEY_DIGITS),
  NAME_ELEMENT(KEY_TEEN), NAME_ELEMENT(KEY_TWEN), NAME_ELEMENT(KEY_DEL_EOL),
  NAME_ELEMENT(KEY_DEL_EOS), NAME_ELEMENT(KEY_INS_LINE),
  NAME_ELEMENT(KEY_DEL_LINE), NAME_ELEMENT(KEY_VIDEOPHONE),
  NAME_ELEMENT(KEY_GAMES), NAME_ELEMENT(KEY_ZOOMIN),
  NAME_ELEMENT(KEY_ZOOMOUT), NAME_ELEMENT(KEY_ZOOMRESET),
  NAME_ELEMENT(KEY_WORDPROCESSOR), NAME_ELEMENT(KEY_EDITOR),
  NAME_ELEMENT(KEY_SPREADSHEET), NAME_ELEMENT(KEY_GRAPHICSEDITOR),
  NAME_ELEMENT(KEY_PRESENTATION), NAME_ELEMENT(KEY_DATABASE),
  NAME_ELEMENT(KEY_NEWS), NAME_ELEMENT(KEY_VOICEMAIL),
  NAME_ELEMENT(KEY_ADDRESSBOOK), NAME_ELEMENT(KEY_MESSENGER),
  NAME_ELEMENT(KEY_DISPLAYTOGGLE), NAME_ELEMENT(KEY_SPELLCHECK),
  NAME_ELEMENT(KEY_LOGOFF), NAME_ELEMENT(KEY_DOLLAR),
  NAME_ELEMENT(KEY_EURO), NAME_ELEMENT(KEY_FRAMEBACK),
  NAME_ELEMENT(KEY_FRAMEFORWARD), NAME_ELEMENT(KEY_CONTEXT_MENU),
  NAME_ELEMENT(KEY_MEDIA_REPEAT), NAME_ELEMENT(KEY_DEL_EOL),
  NAME_ELEMENT(KEY_DEL_EOS), NAME_ELEMENT(KEY_INS_LINE),
  NAME_ELEMENT(KEY_DEL_LINE), NAME_ELEMENT(KEY_FN),
  NAME_ELEMENT(KEY_FN_ESC), NAME_ELEMENT(KEY_FN_F1),
  NAME_ELEMENT(KEY_FN_F2), NAME_ELEMENT(KEY_FN_F3), NAME_ELEMENT(KEY_FN_F4),
  NAME_ELEMENT(KEY_FN_F5), NAME_ELEMENT(KEY_FN_F6), NAME_ELEMENT(KEY_FN_F7),
  NAME_ELEMENT(KEY_FN_F8), NAME_ELEMENT(KEY_FN_F9),
  NAME_ELEMENT(KEY_FN_F10), NAME_ELEMENT(KEY_FN_F11),
  NAME_ELEMENT(KEY_FN_F12), NAME_ELEMENT(KEY_FN_1), NAME_ELEMENT(KEY_FN_2),
  NAME_ELEMENT(KEY_FN_D), NAME_ELEMENT(KEY_FN_E), NAME_ELEMENT(KEY_FN_F),
  NAME_ELEMENT(KEY_FN_S), NAME_ELEMENT(KEY_FN_B),
  NAME_ELEMENT(KEY_BRL_DOT1), NAME_ELEMENT(KEY_BRL_DOT2),
  NAME_ELEMENT(KEY_BRL_DOT3), NAME_ELEMENT(KEY_BRL_DOT4),
  NAME_ELEMENT(KEY_BRL_DOT5), NAME_ELEMENT(KEY_BRL_DOT6),
  NAME_ELEMENT(KEY_BRL_DOT7), NAME_ELEMENT(KEY_BRL_DOT8),
  NAME_ELEMENT(KEY_BRL_DOT9), NAME_ELEMENT(KEY_BRL_DOT10),
  NAME_ELEMENT(KEY_NUMERIC_0), NAME_ELEMENT(KEY_NUMERIC_1),
  NAME_ELEMENT(KEY_NUMERIC_2), NAME_ELEMENT(KEY_NUMERIC_3),
  NAME_ELEMENT(KEY_NUMERIC_4), NAME_ELEMENT(KEY_NUMERIC_5),
  NAME_ELEMENT(KEY_NUMERIC_6), NAME_ELEMENT(KEY_NUMERIC_7),
  NAME_ELEMENT(KEY_NUMERIC_8), NAME_ELEMENT(KEY_NUMERIC_9),
  NAME_ELEMENT(KEY_NUMERIC_STAR), NAME_ELEMENT(KEY_NUMERIC_POUND),
  NAME_ELEMENT(KEY_BATTERY), NAME_ELEMENT(KEY_BLUETOOTH),
  NAME_ELEMENT(KEY_BRIGHTNESS_CYCLE), NAME_ELEMENT(KEY_BRIGHTNESS_ZERO),
  NAME_ELEMENT(KEY_DASHBOARD), NAME_ELEMENT(KEY_DISPLAY_OFF),
  NAME_ELEMENT(KEY_DOCUMENTS), NAME_ELEMENT(KEY_FORWARDMAIL),
  NAME_ELEMENT(KEY_NEW), NAME_ELEMENT(KEY_KBDILLUMDOWN),
  NAME_ELEMENT(KEY_KBDILLUMUP), NAME_ELEMENT(KEY_KBDILLUMTOGGLE),
  NAME_ELEMENT(KEY_REDO), NAME_ELEMENT(KEY_REPLY), NAME_ELEMENT(KEY_SAVE),
  NAME_ELEMENT(KEY_SCALE), NAME_ELEMENT(KEY_SEND),
  NAME_ELEMENT(KEY_SCREENLOCK), NAME_ELEMENT(KEY_SWITCHVIDEOMODE),
  NAME_ELEMENT(KEY_UWB), NAME_ELEMENT(KEY_VIDEO_NEXT),
  NAME_ELEMENT(KEY_VIDEO_PREV), NAME_ELEMENT(KEY_WIMAX),
  NAME_ELEMENT(KEY_WLAN),
#ifdef KEY_RFKILL
  NAME_ELEMENT(KEY_RFKILL),
#endif
#ifdef KEY_WPS_BUTTON
  NAME_ELEMENT(KEY_WPS_BUTTON),
#endif
#ifdef KEY_TOUCHPAD_TOGGLE
  NAME_ELEMENT(KEY_TOUCHPAD_TOGGLE),
  NAME_ELEMENT(KEY_TOUCHPAD_ON),
  NAME_ELEMENT(KEY_TOUCHPAD_OFF),
#endif

  NAME_ELEMENT(BTN_0), NAME_ELEMENT(BTN_1), NAME_ELEMENT(BTN_2),
  NAME_ELEMENT(BTN_3), NAME_ELEMENT(BTN_4), NAME_ELEMENT(BTN_5),
  NAME_ELEMENT(BTN_6), NAME_ELEMENT(BTN_7), NAME_ELEMENT(BTN_8),
  NAME_ELEMENT(BTN_9), NAME_ELEMENT(BTN_LEFT), NAME_ELEMENT(BTN_RIGHT),
  NAME_ELEMENT(BTN_MIDDLE), NAME_ELEMENT(BTN_SIDE),
  NAME_ELEMENT(BTN_EXTRA), NAME_ELEMENT(BTN_FORWARD),
  NAME_ELEMENT(BTN_BACK), NAME_ELEMENT(BTN_TASK), NAME_ELEMENT(BTN_TRIGGER),
  NAME_ELEMENT(BTN_THUMB), NAME_ELEMENT(BTN_THUMB2), NAME_ELEMENT(BTN_TOP),
  NAME_ELEMENT(BTN_TOP2), NAME_ELEMENT(BTN_PINKIE), NAME_ELEMENT(BTN_BASE),
  NAME_ELEMENT(BTN_BASE2), NAME_ELEMENT(BTN_BASE3), NAME_ELEMENT(BTN_BASE4),
  NAME_ELEMENT(BTN_BASE5), NAME_ELEMENT(BTN_BASE6), NAME_ELEMENT(BTN_DEAD),
  NAME_ELEMENT(BTN_A), NAME_ELEMENT(BTN_B), NAME_ELEMENT(BTN_C),
  NAME_ELEMENT(BTN_X), NAME_ELEMENT(BTN_Y), NAME_ELEMENT(BTN_Z),
  NAME_ELEMENT(BTN_TL), NAME_ELEMENT(BTN_TR), NAME_ELEMENT(BTN_TL2),
  NAME_ELEMENT(BTN_TR2), NAME_ELEMENT(BTN_SELECT), NAME_ELEMENT(BTN_START),
  NAME_ELEMENT(BTN_MODE), NAME_ELEMENT(BTN_THUMBL),
  NAME_ELEMENT(BTN_THUMBR), NAME_ELEMENT(BTN_TOOL_PEN),
  NAME_ELEMENT(BTN_TOOL_RUBBER), NAME_ELEMENT(BTN_TOOL_BRUSH),
  NAME_ELEMENT(BTN_TOOL_PENCIL), NAME_ELEMENT(BTN_TOOL_AIRBRUSH),
  NAME_ELEMENT(BTN_TOOL_FINGER), NAME_ELEMENT(BTN_TOOL_MOUSE),
  NAME_ELEMENT(BTN_TOOL_LENS), NAME_ELEMENT(BTN_TOUCH),
  NAME_ELEMENT(BTN_STYLUS), NAME_ELEMENT(BTN_STYLUS2),
  NAME_ELEMENT(BTN_TOOL_DOUBLETAP), NAME_ELEMENT(BTN_TOOL_TRIPLETAP),
  NAME_ELEMENT(BTN_TOOL_QUADTAP), NAME_ELEMENT(BTN_GEAR_DOWN),
  NAME_ELEMENT(BTN_GEAR_UP),

#ifdef BTN_TRIGGER_HAPPY
  NAME_ELEMENT(BTN_TRIGGER_HAPPY1), NAME_ELEMENT(BTN_TRIGGER_HAPPY11),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY2), NAME_ELEMENT(BTN_TRIGGER_HAPPY12),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY3), NAME_ELEMENT(BTN_TRIGGER_HAPPY13),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY4), NAME_ELEMENT(BTN_TRIGGER_HAPPY14),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY5), NAME_ELEMENT(BTN_TRIGGER_HAPPY15),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY6), NAME_ELEMENT(BTN_TRIGGER_HAPPY16),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY7), NAME_ELEMENT(BTN_TRIGGER_HAPPY17),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY8), NAME_ELEMENT(BTN_TRIGGER_HAPPY18),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY9), NAME_ELEMENT(BTN_TRIGGER_HAPPY19),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY10), NAME_ELEMENT(BTN_TRIGGER_HAPPY20),

  NAME_ELEMENT(BTN_TRIGGER_HAPPY21), NAME_ELEMENT(BTN_TRIGGER_HAPPY31),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY22), NAME_ELEMENT(BTN_TRIGGER_HAPPY32),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY23), NAME_ELEMENT(BTN_TRIGGER_HAPPY33),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY24), NAME_ELEMENT(BTN_TRIGGER_HAPPY34),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY25), NAME_ELEMENT(BTN_TRIGGER_HAPPY35),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY26), NAME_ELEMENT(BTN_TRIGGER_HAPPY36),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY27), NAME_ELEMENT(BTN_TRIGGER_HAPPY37),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY28), NAME_ELEMENT(BTN_TRIGGER_HAPPY38),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY29), NAME_ELEMENT(BTN_TRIGGER_HAPPY39),
  NAME_ELEMENT(BTN_TRIGGER_HAPPY30), NAME_ELEMENT(BTN_TRIGGER_HAPPY40),
#endif
};

static const char * const absval[6] = {
  "Value", "Min  ", "Max  ", "Fuzz ", "Flat ", "Resolution " };

static const char * const relatives[REL_MAX + 1] = {
  [0 ... REL_MAX] = NULL,
  NAME_ELEMENT(REL_X), NAME_ELEMENT(REL_Y), NAME_ELEMENT(REL_Z),
  NAME_ELEMENT(REL_RX), NAME_ELEMENT(REL_RY), NAME_ELEMENT(REL_RZ),
  NAME_ELEMENT(REL_HWHEEL), NAME_ELEMENT(REL_DIAL), NAME_ELEMENT(REL_WHEEL),
  NAME_ELEMENT(REL_MISC),
};

static const char * const absolutes[ABS_MAX + 1] = {
  [0 ... ABS_MAX] = NULL,
  NAME_ELEMENT(ABS_X), NAME_ELEMENT(ABS_Y), NAME_ELEMENT(ABS_Z),
  NAME_ELEMENT(ABS_RX), NAME_ELEMENT(ABS_RY), NAME_ELEMENT(ABS_RZ),
  NAME_ELEMENT(ABS_THROTTLE), NAME_ELEMENT(ABS_RUDDER),
  NAME_ELEMENT(ABS_WHEEL), NAME_ELEMENT(ABS_GAS), NAME_ELEMENT(ABS_BRAKE),
  NAME_ELEMENT(ABS_HAT0X), NAME_ELEMENT(ABS_HAT0Y), NAME_ELEMENT(ABS_HAT1X),
  NAME_ELEMENT(ABS_HAT1Y), NAME_ELEMENT(ABS_HAT2X), NAME_ELEMENT(ABS_HAT2Y),
  NAME_ELEMENT(ABS_HAT3X), NAME_ELEMENT(ABS_HAT3Y),
  NAME_ELEMENT(ABS_PRESSURE), NAME_ELEMENT(ABS_DISTANCE),
  NAME_ELEMENT(ABS_TILT_X), NAME_ELEMENT(ABS_TILT_Y),
  NAME_ELEMENT(ABS_TOOL_WIDTH), NAME_ELEMENT(ABS_VOLUME),
  NAME_ELEMENT(ABS_MISC),
#ifdef ABS_MT_BLOB_ID
  NAME_ELEMENT(ABS_MT_TOUCH_MAJOR),
  NAME_ELEMENT(ABS_MT_TOUCH_MINOR),
  NAME_ELEMENT(ABS_MT_WIDTH_MAJOR),
  NAME_ELEMENT(ABS_MT_WIDTH_MINOR),
  NAME_ELEMENT(ABS_MT_ORIENTATION),
  NAME_ELEMENT(ABS_MT_POSITION_X),
  NAME_ELEMENT(ABS_MT_POSITION_Y),
  NAME_ELEMENT(ABS_MT_TOOL_TYPE),
  NAME_ELEMENT(ABS_MT_BLOB_ID),
#endif
#ifdef ABS_MT_TRACKING_ID
  NAME_ELEMENT(ABS_MT_TRACKING_ID),
#endif
#ifdef ABS_MT_PRESSURE
  NAME_ELEMENT(ABS_MT_PRESSURE),
#endif
#ifdef ABS_MT_SLOT
  NAME_ELEMENT(ABS_MT_SLOT),
#endif

};

static const char * const misc[MSC_MAX + 1] = {
  [0 ... MSC_MAX] = NULL,
  NAME_ELEMENT(MSC_SERIAL), NAME_ELEMENT(MSC_PULSELED),
  NAME_ELEMENT(MSC_GESTURE), NAME_ELEMENT(MSC_RAW),
  NAME_ELEMENT(MSC_SCAN),
};

static const char * const leds[LED_MAX + 1] = {
  [0 ... LED_MAX] = NULL,
  NAME_ELEMENT(LED_NUML), NAME_ELEMENT(LED_CAPSL),
  NAME_ELEMENT(LED_SCROLLL), NAME_ELEMENT(LED_COMPOSE),
  NAME_ELEMENT(LED_KANA), NAME_ELEMENT(LED_SLEEP),
  NAME_ELEMENT(LED_SUSPEND), NAME_ELEMENT(LED_MUTE), NAME_ELEMENT(LED_MISC),
};

static const char * const repeats[REP_MAX + 1] = {
  [0 ... REP_MAX] = NULL,
  NAME_ELEMENT(REP_DELAY), NAME_ELEMENT(REP_PERIOD)
};

static const char * const sounds[SND_MAX + 1] = {
  [0 ... SND_MAX] = NULL,
  NAME_ELEMENT(SND_CLICK), NAME_ELEMENT(SND_BELL), NAME_ELEMENT(SND_TONE)
};

static const char * const syns[3] = {
  NAME_ELEMENT(SYN_REPORT), NAME_ELEMENT(SYN_CONFIG),
#ifdef SYN_MT_REPORT
  NAME_ELEMENT(SYN_MT_REPORT)
#endif
};

static const char * const switches[SW_MAX + 1] = {
  [0 ... SW_MAX] = NULL,
  NAME_ELEMENT(SW_LID), NAME_ELEMENT(SW_TABLET_MODE),
  NAME_ELEMENT(SW_HEADPHONE_INSERT), NAME_ELEMENT(SW_RFKILL_ALL),
  NAME_ELEMENT(SW_MICROPHONE_INSERT), NAME_ELEMENT(SW_DOCK),
  NAME_ELEMENT(SW_LINEOUT_INSERT), NAME_ELEMENT(SW_JACK_PHYSICAL_INSERT),
#ifdef SW_VIDEOOUT_INSERT
  NAME_ELEMENT(SW_VIDEOOUT_INSERT),
#endif
#ifdef SW_CAMERA_LENS_COVER
  NAME_ELEMENT(SW_CAMERA_LENS_COVER),
  NAME_ELEMENT(SW_KEYPAD_SLIDE),
  NAME_ELEMENT(SW_FRONT_PROXIMITY),
#endif
#ifdef SW_ROTATE_LOCK
  NAME_ELEMENT(SW_ROTATE_LOCK),
#endif
};

static const char * const force[FF_MAX + 1] = {
  [0 ... FF_MAX] = NULL,
  NAME_ELEMENT(FF_RUMBLE), NAME_ELEMENT(FF_PERIODIC),
  NAME_ELEMENT(FF_CONSTANT), NAME_ELEMENT(FF_SPRING),
  NAME_ELEMENT(FF_FRICTION), NAME_ELEMENT(FF_DAMPER),
  NAME_ELEMENT(FF_INERTIA), NAME_ELEMENT(FF_RAMP), NAME_ELEMENT(FF_SQUARE),
  NAME_ELEMENT(FF_TRIANGLE), NAME_ELEMENT(FF_SINE), NAME_ELEMENT(FF_SAW_UP),
  NAME_ELEMENT(FF_SAW_DOWN), NAME_ELEMENT(FF_CUSTOM), NAME_ELEMENT(FF_GAIN),
  NAME_ELEMENT(FF_AUTOCENTER),
};

static const char * const forcestatus[FF_STATUS_MAX + 1] = {
  [0 ... FF_STATUS_MAX] = NULL,
  NAME_ELEMENT(FF_STATUS_STOPPED), NAME_ELEMENT(FF_STATUS_PLAYING),
};

static const char * const * const names[EV_MAX + 1] = {
  [0 ... EV_MAX] = NULL,
  [EV_SYN] = events, [EV_KEY] = keys, [EV_REL] = relatives,
  [EV_ABS] = absolutes, [EV_MSC] = misc, [EV_LED] = leds, [EV_SND] = sounds,
  [EV_REP] = repeats, [EV_SW] = switches, [EV_FF] = force,
  [EV_FF_STATUS] = forcestatus,
};


struct mt_slot {
  int track_id;
  int touch_major;
  int touch_minor;
  int width_major;
  int width_minor;
  int orientation;
  int x;
  int y;
  int pressure;
  int tool_type;
};

struct mt_state {
  int current;
  struct mt_slot *slot;
};

typedef struct {
  uint32_t code;
  int32_t values[MAX_SLOT_COUNT];
} MTSlotInfo, *MTSlotInfoPtr;


// Program Options
bool clickclear = true;
bool monotonic = true;
bool persist = true;
bool showclicks = false;
bool single_pressure = false;

static Display *dpy;
static unsigned long blackColor;
static Window w;
static GC gc;
static Colormap colormap;
const char * const color_str[] = {
  "#0000FF",  // Blue
  "#00FF00",  // Green
  "#FF0000",  // Red
  "#FFFF00",  // Yellow
  "#00FFFF",  // Cyan
  "#FF00FF",  // Magenta
  "#FFFFFF",  // White
  "#FF8040",  // Orange
  "#804000",  // Brown
  "#808080",  // Grey
};
#define COLOR_COUNT ARRAY_SIZE(color_str)
static XColor color[COLOR_COUNT];
#define WHITE_INDEX 6

static int slot_min = 0;
static int slot_max = 10;
// TODO: Update x/y_min/max on window resize
static int x_min = 0;
static int x_max = 0;
static int y_min = 0;
static int y_max = 0;
static int pressure_min = 0;
static int pressure_max = 255;
static int touch_major_min = 0;
static int touch_major_max = 255;

// For aspect ratio preservation
static float pad_to_pixel = 1.0;
static int pad_x_offset = 0;
static int pad_y_offset = 0;

static bool single_pressure_device;
static bool has_mt_slot = false;

static unsigned int w_width;
static unsigned int w_height;


static void MtClear() {
  XClearWindow(dpy, w);

  XSetForeground(dpy, gc, color[WHITE_INDEX].pixel);
  XSetLineAttributes(dpy, gc, 1, LineSolid, CapRound, JoinRound);
  XDrawRectangle(dpy, w, gc, pad_x_offset, pad_y_offset,
                 w_width - pad_x_offset * 2 - 1,
                 w_height - pad_y_offset * 2 - 1);
  XFlush(dpy);
}


static Display *InitDisplay(const char *displayname, const char *geometry) {
  Window root;
  int x, y;
  unsigned int border_width;
  unsigned int depth;
  int i;

  dpy = XOpenDisplay(displayname);
  if (!dpy)
    return NULL;

  blackColor = BlackPixel(dpy, DefaultScreen(dpy));

  // Determine the window geometry
  if (!geometry || XParseGeometry(geometry, &x, &y, &w_width, &w_height) == 0) {
    x = y = 0;
    w_width = DisplayWidth(dpy, 0);
    w_height = DisplayHeight(dpy, 0);
  }

  // Create a window
  w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), x, y, w_width, w_height,
                          0, blackColor, blackColor);

  // Set the application name so that it is easier to look up in window manager.
  XStoreName(dpy, w, "mtplot");

  // We want to get MapNotify events
  XSelectInput(dpy, w, StructureNotifyMask | KeyPressMask);

  // "Map" the window (that is, make it appear on the screen)
  XMapWindow(dpy, w);

  // Create a "Graphics Context"
  gc = XCreateGC(dpy, w, 0, NULL);

  colormap = DefaultColormap(dpy, DefaultScreen(dpy));
  for (i = 0; i < COLOR_COUNT; i++) {
    XParseColor(dpy, colormap, color_str[i], &color[i]);
    XAllocColor(dpy, colormap, &color[i]);
  }

  // Wait for the MapNotify event
  while (1) {
    XEvent e;
    XNextEvent(dpy, &e);
    if (e.type == MapNotify)
      break;
  }

  XGetGeometry(dpy, w, &root, &x, &y, &w_width, &w_height, &border_width,
               &depth);

  // TODO: Resize touch surface to 90% of screen, and draw a virtual bezel
  //       since Touchpads sometimes emit events outside advertised range.

  return dpy;
}

static void MtSlotInit(struct mt_slot *s) {
  memset(s, 0, sizeof(struct mt_slot));
  s->track_id = -1;
}

static void InitPreserveRatio() {
  float pad_width= x_max - x_min + 1;
  float pad_height = y_max - y_min + 1;

  // Compute pad to window scaling factors
  float pad_x_scale = w_width / pad_width;
  float pad_y_scale = w_height / pad_height;
  // Choose smaller scaling factor for both x & y
  pad_to_pixel = MIN(pad_x_scale, pad_y_scale);
  // Resulting pixel sizes:
  float pad_pixel_width = pad_width * pad_to_pixel;
  float pad_pixel_height = pad_height * pad_to_pixel;
  // Compute offsets to center pad in window
  pad_x_offset = (w_width - pad_pixel_width) / 2;
  pad_y_offset = (w_height - pad_pixel_height) / 2;
}

static void MtSlotPaint(struct mt_slot *s) {
  int x_pixel, y_pixel;
  float width, height;
  unsigned long forecolor;

  if (s->track_id == -1)
    return;

  if (s->pressure) {
    // TODO: Get really fancy and use touch_minor & orientation
    // TODO: Get really really fancy, and use 'width_*'
    width = 50.0 *
        (float)(s->pressure - pressure_min) / (pressure_max - pressure_min + 1);
    height = 50.0 *
        (float)(s->pressure - pressure_min) / (pressure_max - pressure_min + 1);
  } else if (s->touch_major) {
    // In case we don't have pressure reading, use touch_major instead.
    width = 50.0 * (float)(s->touch_major - touch_major_min) /
        (touch_major_max - touch_major_min + 1);
    height = 50.0 * (float)(s->touch_major - touch_major_min) /
        (touch_major_max - touch_major_min + 1);
  } else {
    width = 10;
    height = 10;
  }

  x_pixel = s->x * pad_to_pixel + pad_x_offset;
  y_pixel = s->y * pad_to_pixel + pad_y_offset;

  // TODO: Get fancy and adjust color intensity using pressure
  forecolor = color[s->track_id % COLOR_COUNT].pixel;
  XSetForeground(dpy, gc, forecolor);

  XFillArc(dpy, w, gc, x_pixel, y_pixel, width, height, 0, 360 * 64);
}

int ProbeAbsInfo(int fd, struct input_absinfo *absinfo, size_t key) {
  if (ioctl(fd, EVIOCGABS(key), absinfo) < 0) {
    perror("Error in getting info in ProbeAbsInfo");
    return false;
  }
  return true;
}

int ProbeMTSlot(int fd, MTSlotInfoPtr req, uint32_t code) {
  req->code = code;
  if (ioctl(fd, EVIOCGMTSLOTS((sizeof(*req))), req) < 0) {
    perror("Error in getting info in ProbeMTSlot");
    return false;
  }
  return true;
}

static void MtStateInit(struct mt_state *s, char *filename) {
  int i;
  int fd = open(filename, O_RDWR | O_NONBLOCK, 0);
  struct input_absinfo absinfo;
  struct mt_slot *current_slot;
  MTSlotInfo req;

  s->current = 0;
  s->slot = calloc(slot_max - slot_min + 1, sizeof(struct mt_slot));
  for (i = slot_min; i <= slot_max; i++)
    MtSlotInit(&s->slot[i - slot_min]);

  // Synchronize the last slot number with the kernel evdev driver.
  if (ProbeAbsInfo(fd, &absinfo, ABS_MT_SLOT))
    s->current = absinfo.value;
  current_slot = &s->slot[s->current];

  // Synchronize last X with the kernel evdev driver.
  if (ProbeMTSlot(fd, &req, ABS_MT_POSITION_X))
    current_slot->x = req.values[s->current];

  // Synchronize last Y with the kernel evdev driver.
  if (ProbeMTSlot(fd, &req, ABS_MT_POSITION_Y))
    current_slot->y = req.values[s->current];

  // Synchronize last PRESSURE with the kernel evdev driver.
  if (ProbeMTSlot(fd, &req, ABS_MT_PRESSURE))
    current_slot->pressure  = req.values[s->current];
}

static void MtStatePaint(struct mt_state *s) {
  int i;
  if (!persist)
    MtClear(dpy, w);

  for (i = slot_min; i <= slot_max; i++)
    MtSlotPaint(&s->slot[i - slot_min]);
  XFlush(dpy);
}

static void MtClickPaint(struct mt_state *s) {
  int i;

  for (i = slot_min; i <= slot_max; i++) {
    int x_pixel, y_pixel;
    unsigned long forecolor;
    struct mt_slot finger = s->slot[i - slot_min];

    if (finger.track_id == -1)
        continue;

    y_pixel = finger.y * pad_to_pixel + pad_y_offset;
    x_pixel = finger.x * pad_to_pixel + pad_x_offset;
    forecolor = color[finger.track_id % COLOR_COUNT].pixel;

    XSetForeground(dpy, gc, forecolor);
    XSetLineAttributes(dpy, gc, 5, LineSolid, CapRound, JoinRound);
    XDrawRectangle(dpy, w, gc, x_pixel, y_pixel, 15, 15);
  }
  XFlush(dpy);
}

static void ProcessSynReport(struct mt_state *s, struct input_event *e) {
  MtStatePaint(s);
}


static void ProcessKey(struct mt_state *s, struct input_event *e) {
  if (e->code == BTN_LEFT) {
    if (clickclear)
        MtClear();
    if (showclicks)
        MtClickPaint(s);
  }
}

// Update all active slots with the same ABS_PRESSURE value as it is a
// semi-mt device.
static void UpdateWithAbsPressure(struct mt_state *s, struct input_event *e) {
  int i;
  for (i = slot_min; i <= slot_max; i++) {
    struct mt_slot *slt = &s->slot[i];
    if (slt->track_id != -1)
      slt->pressure = e->value;
  }
}

// Associate the track ID with slot number.
static int track_id_to_slot(struct mt_state *s, int track_id) {
  int i;
  // For existing track ID, return its associated slot.
  for (i = slot_min; i <= slot_max; i++)
    if (s->slot[i].track_id == track_id)
      return i;

  // For new track ID, return a free slot.
  for (i = slot_min; i <= slot_max; i++)
    if (s->slot[i].track_id == -1)
      return i;

  perror("No free slot for for new track_id");
  return slot_min;
}

static void ProcessAbs(struct mt_state *s, struct input_event *e) {
  struct mt_slot *slot = &s->slot[s->current];

  switch (e->code) {
    case ABS_MT_SLOT:
      s->current = e->value;
      break;
    case ABS_MT_TOUCH_MAJOR:
      // If there is no ABS_MT_SLOT support, use a 0 touch major as
      // the indication of removed touch contact.
      if (!has_mt_slot && e->value == 0)
        slot->track_id = -1;
      slot->touch_major = e->value;
      break;
    case ABS_MT_TOUCH_MINOR:
      slot->touch_minor = e->value;
      break;
    case ABS_MT_WIDTH_MAJOR:
      slot->width_major = e->value;
      break;
    case ABS_MT_WIDTH_MINOR:
      slot->width_minor = e->value;
      break;
    case ABS_MT_ORIENTATION:
      slot->orientation = e->value;
      break;
    case ABS_MT_POSITION_X:
      slot->x = e->value;
      break;
    case ABS_MT_POSITION_Y:
      slot->y = e->value;
      break;
    case ABS_MT_TOOL_TYPE:
      slot->tool_type = e->value;
      break;
    case ABS_MT_BLOB_ID:
      break;
    case ABS_MT_TRACKING_ID:
      // If there is no ABS_MT_SLOT support, use ABS_MT_TRACKING_ID as
      // replacement. If new ABS_MT_TRACKING_ID is received, update the
      // current slot to be the one associated with the new track ID.
      if (!has_mt_slot && e->value >= 0) {
        s->current = track_id_to_slot(s, e->value);
        slot = &s->slot[s->current];
      }
      slot->track_id = e->value;
      break;
    case ABS_MT_PRESSURE:
      slot->pressure = single_pressure ? 3 : e->value;
      break;
    case ABS_PRESSURE:
      if (single_pressure_device)
        UpdateWithAbsPressure(s, e);
      break;
    default:
      break;
  }
}

static void MtStateUpdate(struct mt_state *s, struct input_event *e) {
  if (e->type == EV_SYN && e->code == SYN_REPORT)
    ProcessSynReport(s, e);
  else if (e->type == EV_KEY)
    ProcessKey(s, e);
  else if (e->type == EV_ABS)
    ProcessAbs(s, e);
}

// Filter for the AutoDevProbe scandir on /dev/input.
//
// @param dir The current directory entry provided by scandir.
//
// @return Non-zero if the given directory entry starts with "event", or zero
// otherwise.
static int IsEventDevice(const struct dirent *dir) {
  return strncmp(EVENT_DEV_NAME, dir->d_name, 5) == 0;
}

// Scans all /dev/input/event*, display them and ask the user which one to
// open.
//
// @return The event device file name of the device file selected. This
// string is allocated and must be freed by the caller.
static char *ScanDevices(void) {
  struct dirent **namelist;
  int i, ndev, devnum, rc;
  char *filename;

  ndev = scandir(DEV_INPUT_EVENT, &namelist, IsEventDevice, alphasort);
  if (ndev <= 0)
    return NULL;

  fprintf(stderr, "Available devices:\n");

  for (i = 0; i < ndev; i++)
  {
    char fname[64];
    int fd = -1;
    char name[256] = "???";

    snprintf(fname, sizeof(fname), "%s/%s", DEV_INPUT_EVENT,
             namelist[i]->d_name);
    fd = open(fname, O_RDONLY);
    if (fd < 0)
      continue;
    ioctl(fd, EVIOCGNAME(sizeof(name)), name);

    fprintf(stderr, "%s:\t%s\n", fname, name);
    close(fd);
    free(namelist[i]);
  }

  fprintf(stderr, "Select the device event number [0-%d]: ", ndev - 1);
  rc = scanf("%d", &devnum);

  if (rc != 1 || devnum >= ndev || devnum < 0)
    return NULL;

  rc = asprintf(&filename, "%s/%s%d",
                DEV_INPUT_EVENT, EVENT_DEV_NAME, devnum);
  if (rc == -1)
    return NULL;

  return filename;
}

// Print additional information for absolute axes (min/max, current value,
// etc.).
//
// @param fd The file descriptor to the device.
// @param axis The axis identifier (e.g. ABS_X).
static void PrintAbsData(int fd, int axis) {
  int abs[6] = {0};
  int k;

  ioctl(fd, EVIOCGABS(axis), abs);
  for (k = 0; k < 6; k++)
    if ((k < 3) || abs[k]) {
      printf("      %s %6d\n", absval[k], abs[k]);
      if (k == 1) {
        if (axis == ABS_MT_SLOT)
          slot_min = abs[k];
        else if (axis == ABS_MT_POSITION_X)
          x_min = abs[k];
        else if (axis == ABS_MT_POSITION_Y)
          y_min = abs[k];
        else if (axis == ABS_MT_PRESSURE)
          pressure_min = abs[k];
        else if (axis == ABS_PRESSURE)
          pressure_min = abs[k];
        else if (axis == ABS_MT_TOUCH_MAJOR)
          touch_major_min = abs[k];
      } else if (k == 2) {
        if (axis == ABS_MT_SLOT)
          slot_max = abs[k];
        else if (axis == ABS_MT_POSITION_X)
          x_max = abs[k];
        else if (axis == ABS_MT_POSITION_Y)
          y_max = abs[k];
        else if (axis == ABS_MT_PRESSURE)
          pressure_max = abs[k];
        else if (axis == ABS_PRESSURE)
          pressure_max = abs[k];
        else if (axis == ABS_MT_TOUCH_MAJOR)
          touch_major_max = abs[k];
      }
    }
}

// Test if the device supports single-pressure value only. If so, we will
// retrieve the pressure value from the element ABS_PRESSURE instead.
//
// @param fd The file descriptor to the device.
// @return false if the bit ABS_MT_PRESSURE is set, or true otherwise.
static bool IsSinglePressureDevice(int fd) {
  unsigned long bits[NBITS(KEY_MAX)];
  memset(bits, 0, sizeof(bits));
  ioctl(fd, EVIOCGBIT(EV_ABS, EV_MAX), bits);
  return (!test_bit(ABS_MT_PRESSURE, bits) && test_bit(ABS_PRESSURE, bits));
}

// Print static device information (no events). This information includes
// version numbers, device name and all bits supported by this device.
//
// @param fd The file descriptor to the device.
// @return 0 on success or 1 otherwise.
static int PrintDeviceInfo(int fd) {
  int i, j;
  int version;
  unsigned short id[4];
  char name[256] = "Unknown";
  unsigned long bit[EV_MAX][NBITS(KEY_MAX)];

  if (ioctl(fd, EVIOCGVERSION, &version)) {
    perror("mtplot: can't get version");
    return 1;
  }

  printf("Input driver version is %d.%d.%d\n",
         version >> 16, (version >> 8) & 0xff, version & 0xff);

  ioctl(fd, EVIOCGID, id);
  printf("Input device ID: bus 0x%x vendor 0x%x product 0x%x version 0x%x\n",
         id[ID_BUS], id[ID_VENDOR], id[ID_PRODUCT], id[ID_VERSION]);

  ioctl(fd, EVIOCGNAME(sizeof(name)), name);
  printf("Input device name: \"%s\"\n", name);

  memset(bit, 0, sizeof(bit));
  ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
  printf("Supported events:\n");

  for (i = 0; i < EV_MAX; i++)
    if (test_bit(i, bit[0])) {
      printf("  Event type %d (%s)\n", i, events[i] ? events[i] : "?");
      if (!i)
        continue;
      ioctl(fd, EVIOCGBIT(i, KEY_MAX), bit[i]);
      for (j = 0; j < KEY_MAX; j++)
        if (test_bit(j, bit[i])) {
          printf("    Event code %d (%s)\n", j,
                 names[i] ? (names[i][j] ? names[i][j] : "?") : "?");
          if (i == EV_ABS) {
            PrintAbsData(fd, j);
            if (j == ABS_MT_SLOT)
              has_mt_slot = true;
          }
        }
    }

  return 0;
}

// Grab and immediately ungrab the device.
//
// @param fd The file descriptor to the device.
// @return 0 if the grab was successful, or 1 otherwise.
static int TestGrab(int fd) {
  int rc;

  rc = ioctl(fd, EVIOCGRAB, (void *)1);

  if (!rc)
    ioctl(fd, EVIOCGRAB, (void *)0);

  return rc;
}

// Dump the screen shot
//
// @param displayname The display name.
// @param w The mtplot window.
// @param output The screenshot output directory.
static void DumpScreenShot(char *displayname, Window w, char *output) {
  char display_str[16];
  char filename[PATH_MAX];
  char dump_str[PATH_MAX + 64];
  char command[PATH_MAX + 128];
  XID wid = (XID) w;
  char *xauthority_str = "XAUTHORITY=/home/chronos/.Xauthority ";
  struct timeval curr_time;

  if (system("which import > /dev/null 2>&1") != 0) {
    printf("Warning: The screenshot executable (import) does not exist.\n");
    return;
  }

  // If the display is not specified as a command line option, get it
  // from the environment.
  if (displayname == NULL)
    displayname = getenv("DISPLAY");
  sprintf(display_str, "DISPLAY=%s ", displayname);

  // The screenshot file name could be distinguished with the last event time.
  gettimeofday(&curr_time, NULL);
  sprintf(filename, "%s/mtplot_%ld.%06ld.png", output, curr_time.tv_sec,
          curr_time.tv_usec);

  // Use the "import" utility to dump the mtplot window.
  sprintf(dump_str, "import -window %ld %s", wid, filename);

  strcpy(command, display_str);
  strcat(command, xauthority_str);
  strcat(command, dump_str);
  if (system(command) == 0)
    printf("The screenshot is saved as '%s'\n", filename);
  else
    perror("import command error");
}

static void EnableMonotonicTimestamps(int fd)
{
  unsigned int clk = CLOCK_MONOTONIC;
  int ret = ioctl(fd, EVIOCSCLOCKID, &clk);
  if (ret < 0)
    fprintf(stderr, "Monotonic timestamps not supported in this kernel.\n");
}

static const struct option long_options[] = {
  { "clickclear", optional_argument, NULL, 'c' },
  { "display", required_argument, NULL, 'd' },
  { "geometry", optional_argument, NULL, 'g' },
  { "monotonic", optional_argument, NULL, 'm' },
  { "output", optional_argument, NULL, 'o' },
  { "persist", optional_argument, NULL, 'p' },
  { "showclicks", optional_argument, NULL, 's' },
  { "singlepressure", optional_argument, NULL, 'a' },
  { 0, },
};

// Create the specified directory.
int MkDir(char *dir, int mode) {
  struct stat st;

  if (stat(dir, &st) != 0 && mkdir(dir, mode) != 0) {
      perror("Error in making screenshot output directory");
      return false;
  }
  return true;
}

// Create intermediate directories.
int MkDirs(char *path, int mode) {
  char int_path[PATH_MAX];
  char *p = strcpy(int_path, path);

  // Skip the initial '/' if any.
  while (*p == '/')
    p++;

  // Make the intermediate directories if not existing yet.
  while (p = strchr(p, '/')) {
    *p = '\0';
    if (!MkDir(int_path, mode))
      return false;
    *p++ = '/';
  }

  // Need to mkdir the full path too.
  return MkDir(path, mode);
}

// Check if it has the write access to the specified directory.
// Create the intermediate directories if needed.
//
// @param output The screenshot output directory.
bool CheckDirectory(char *output) {
  struct stat st;

  // Check if output already exists.
  if (stat(output, &st) == 0) {
    // Check if output is a regular directory.
    if (S_ISDIR(st.st_mode)) {
      // check its write permission.
      if (st.st_mode & S_IWUSR) {
        return true;
      } else {
        printf("Error: no write permission to '%s'.\n", output);
        return false;
      }
    } else {
      printf("Error: the output '%s' must be a directory.\n", output);
      return false;
    }
  } else {
    // Try to create the output directory if it does not exist yet.
    return MkDirs(output, OUTPUT_DIR_MODE);
  }
}

// Print usage information.
static int Usage(void) {
  printf("USAGE:\n");
  printf("   %s /dev/input/eventX [--display DISPLAY] [--monotonic] [--persist]\n",
         program_invocation_short_name);
  printf("\n");
  printf(" -c, --clickclear[=0|1] =1: clicks clear the screen (default)\n"
         "                        =0: clicks do not clear the screen\n"
         "                            To toggle at runtime press 'c'\n");
  printf(" -g, --geometry[=wxh+x+y] : sets the window geometry\n");
  printf("                            e.g., 1280x800+0+0\n");
  printf(" -m, --monotonic[=0|1]  =1: requests input events with monotonic timestamps\n"
         "                            if supported by the kernel (default)\n"
         "                        =0: request input events with wallclock timestamps\n");
  printf(" -o, --output[=<dir>]     : specifies the screenshot output directory\n"
         "                            (default: /tmp)\n");
  printf(" -p, --persist[=0|1]    =1: display persistent trails for each contact (default)\n"
         "                        =0: only display the current contact positions\n"
         "                            To toggle at runtime press 'p'\n");
  printf(" -s, --showclicks[=0|1] =1: clicks are shown visibly marked on screen\n"
         "                        =0: clicks do not leave a visible mark\n");
  printf(" -a, --singlepressure[=0|1] =1: "
         "All points are drawn with same pressure\n"
         "                            =0: "
         "Point size indicates pressure (default)\n");

  return EXIT_FAILURE;
}

int main(int argc, char **argv) {
  const char *device = NULL;
  char *displayname = NULL;
  char *filename;
  char *geometry;
  struct input_event ev[64];
  int i, rd;
  struct mt_state mt_state;
  int fd, x11_fd, max_fd;
  fd_set rdfs;
  char output[PATH_MAX];

  strcpy(output, DEFAULT_OUTPUT_DIR);
  /* Set DISPLAY to :0 if it's not already set */
  setenv("DISPLAY", ":0", 0);

  while (1) {
    int c = getopt_long_only(argc, argv, "c:d:g:m:o:p:s:", long_options, NULL);
    if (c == -1)
      break;
    switch (c) {
      case 'a':
        single_pressure = (optarg) ? atoi(optarg) : true;
        break;
      case 'c':
        clickclear = (optarg) ? atoi(optarg) : true;
        break;
      case 'd':
        displayname = optarg;
        break;
      case 'g':
        geometry = optarg;
        break;
      case 'm':
        monotonic = (optarg) ? atoi(optarg) : true;
        break;
      case 'o':
        strcpy(output, optarg);
        if (!CheckDirectory(output))
          return EXIT_FAILURE;
        break;
      case 'p':
        persist = (optarg) ? atoi(optarg) : true;
        break;
      case 's':
        showclicks = (optarg) ? atoi(optarg) : true;
        break;
      default:  // Fall through case
        printf("Unknown option '%c'\n", c);
      case '?':
        return Usage();
    }
  }

  if (optind < argc)
    device = argv[optind++];

  if (!device) {
    fprintf(stderr, "No device specified, trying to scan all of %s/%s*\n",
            DEV_INPUT_EVENT, EVENT_DEV_NAME);

    if (getuid() != 0)
      fprintf(stderr, "Not running as root, no devices may be available.\n");

    filename = ScanDevices();
    if (!filename)
      return Usage();
  } else {
    filename = strdup(device);
  }

  if (!filename)
    return EXIT_FAILURE;

  if ((fd = open(filename, O_RDONLY)) < 0) {
    perror("mtplot");
    if (errno == EACCES && getuid() != 0)
      fprintf(stderr, "You do not have access to %s. Try "
              "running as root instead.\n",
              filename);
    return EXIT_FAILURE;
  }

  if (!isatty(fileno(stdout)))
    setbuf(stdout, NULL);

  if (PrintDeviceInfo(fd))
    return EXIT_FAILURE;

  single_pressure_device = IsSinglePressureDevice(fd);

  if (monotonic)
    EnableMonotonicTimestamps(fd);

  printf("Testing ... (interrupt to exit)\n");

  if (TestGrab(fd)) {
    printf("***********************************************\n");
    printf("  This device is grabbed by another process.\n");
    printf("  No events are available to mtplot while the\n");
    printf("  other grab is active.\n");
    printf("  In most cases, this is caused by an X driver,\n");
    printf("  try VT-switching and re-run mtplot again.\n");
    printf("***********************************************\n");
  }

  dpy = InitDisplay(displayname, geometry);
  if (!dpy) {
    fprintf (stderr, "mtplot:  unable to open display '%s'\n",
             XDisplayName (displayname));
    return EXIT_FAILURE;
  }

  MtStateInit(&mt_state, filename);
  InitPreserveRatio();
  free(filename);

  printf("If the display isn't showing, this command may help:\n"
         "echo 0 > /sys/module/i915/parameters/i915_enable_fbc\n");

  x11_fd = ConnectionNumber(dpy);
  max_fd = (x11_fd > fd) ? x11_fd : fd;

  MtClear();
  while (1) {
    int rc;

    // Add device and X file descriptors to fd_set
    FD_ZERO(&rdfs);
    FD_SET(x11_fd, &rdfs);
    FD_SET(fd, &rdfs);

    rc = select(max_fd + 1, &rdfs, NULL, NULL, NULL);
    if (rc == -1)
      perror("select()");

    if (FD_ISSET(fd, &rdfs)) {
      rd = read(fd, ev, sizeof(struct input_event) * 64);

      if (rd < (int) sizeof(struct input_event)) {
        printf("expected %d bytes, got %d\n",
               (int) sizeof(struct input_event), rd);
        perror("\nmtplot: error reading");
        return EXIT_FAILURE;
      }

      for (i = 0; i < rd / sizeof(struct input_event); i++) {
        printf("Event: time %ld.%06ld, ", ev[i].time.tv_sec,
               ev[i].time.tv_usec);

        if (ev[i].type == EV_SYN) {
          if (ev[i].code == SYN_MT_REPORT)
            printf("++++++++++++++ %s ++++++++++++\n", syns[ev[i].code]);
          else
            printf("-------------- %s ------------\n", syns[ev[i].code]);
        } else {
          int type = ev[i].type;
          int code = ev[i].code;
          int value = ev[i].value;
          printf("type %d (%s), code %d (%s), ", type, events[type] ?: "?",
                 code, names[type] ? (names[type][code] ?: "?") : "?");
          if (type == EV_MSC && (code == MSC_RAW || code == MSC_SCAN))
            printf("value %02x\n", value);
          else
            printf("value %d\n", value);
        }
        MtStateUpdate(&mt_state, &ev[i]);
      }
    }

    if (FD_ISSET(x11_fd, &rdfs)) {
      while (XPending(dpy)) {
        XEvent ev;
        XNextEvent(dpy, &ev);
        if (ev.type == ConfigureNotify) {
          XConfigureEvent xce = ev.xconfigure;
          // Save new window dimensions
          w_width = xce.width;
          w_height = xce.height;
        } else if (ev.type == KeyPress) {
          XKeyEvent xke = ev.xkey;
          KeySym ks = XLookupKeysym(&xke, 0);
          if (ks == XK_Escape)
            MtClear();
          else if (ks == XK_a)
            single_pressure = !single_pressure;
          else if (ks == XK_c)
            clickclear = !clickclear;
          else if (ks == XK_p)
            persist = !persist;
          else if (ks == XK_q)
            return 0;
          else if (ks == XK_s)
            DumpScreenShot(displayname, w, output);
        }
      }
    }
  }
}
